package com.atguigu.tingshu.user.service.impl;

import java.util.Date;

import java.util.*;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.user.*;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.user.UserCollectVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Maps;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.catalina.User;
import org.bson.types.ObjectId;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * JWT（JSON Web Token）通常分为三部分，每一部分都有特定的结构和作用。以下是 JWT 的三部分及其存储内容和作用：
 *
 * 1. **Header（头部）**
 *    - **存储内容**：通常包含令牌的类型（如 JWT）以及所使用的签名算法（如 HMAC SHA256 或 RSA）。
 *    - **作用**：定义了生成签名的方式，接收方可以根据此信息验证签名的真实性。
 *
 * 2. **Payload（载荷/声明）**
 *    - **存储内容**：包含声明（Claims），这些声明可以是关于实体（如用户）或其他自定义数据。声明分为三种类型：
 *      - **Registered claims（注册声明）**：预定义的声明，如 `iss`（发行人）、`exp`（过期时间）、`sub`（主题）等。
 *      - **Public claims（公共声明）**：可自定义的声明，但为了避免冲突，建议在 IANA 中注册或使用前缀。
 *      - **Private claims（私有声明）**：为共享信息创建的自定义声明，仅在同意使用它们的各方之间共享。
 *    - **作用**：携带实际的数据信息，用于身份验证、信息传递等。
 *
 * 3. **Signature（签名）**
 *    - **存储内容**：通过对 Header 和 Payload 进行 Base64Url 编码后，使用指定的算法（如 HMAC SHA256 或 RSA）进行加密签名。
 *    - **作用**：确保令牌在传输过程中未被篡改，并验证发送方的身份（如果使用的是私钥签名）。
 *
 * 通过这三部分，JWT 实现了安全的信息传递和身份验证机制。
 * ### 公钥和私钥的作用
 *
 * 在非对称加密算法（如 RSA）中，公钥和私钥有以下作用：
 *
 * 1. **私钥加签**
 *    - 私钥用于生成签名。
 *    - 在 JWT（JSON Web Token）的生成过程中，`Signature` 部分是通过对 `Header` 和 `Payload` 的编码结果进行签名生成的。具体过程为：
 *      ```
 *      Signature = HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(Payload), privateKey)
 *      ```
 *
 *    - 这里的 `privateKey` 是私钥，用于确保只有持有私钥的一方才能生成合法的签名。
 *
 * 2. **公钥验签**
 *    - 公钥用于验证签名的合法性。
 *    - 在 JWT 的验证过程中，接收方会使用公钥来验证 `Signature` 是否由合法的私钥生成。具体过程为：
 *      ```
 *      Verify(base64UrlEncode(Header) + "." + base64UrlEncode(Payload), Signature, publicKey)
 *      ```
 *
 *    - 如果签名验证通过，则说明该 JWT 是由合法的私钥生成的，且数据未被篡改。
 *
 * ---
 *
 * ### 加签和验签的具体操作
 *
 * 1. **加签（签名生成）**
 *    - **输入**：JWT 的 `Header` 和 `Payload`，以及私钥。
 *    - **过程**：将 `Header` 和 `Payload` 按照特定格式拼接后，使用私钥对其进行哈希运算并生成签名。
 *    - **输出**：生成的 `Signature` 作为 JWT 的第三部分。
 *
 * 2. **验签（签名验证）**
 *    - **输入**：JWT 的 `Header`、`Payload`、`Signature`，以及公钥。
 *    - **过程**：重新计算 `Header` 和 `Payload` 的哈希值，并与 `Signature` 进行对比，同时使用公钥验证签名是否由对应的私钥生成。
 *    - **输出**：如果验证通过，则返回 true；否则返回 false。
 *
 * ---
 *
 * ### 总结
 *
 * - **私钥**：用于加签，确保只有持有私钥的一方可以生成合法的签名。
 * - **公钥**：用于验签，确保任何人都可以验证签名的合法性，而无需知道私钥。
 * - **加签**：生成签名，保护数据的完整性和真实性。
 * - **验签**：验证签名，确保数据未被篡改且来源可信。
 * 在非对称加密体系中：
 * - **私钥用于加密（加签）**：数据发送方使用私钥对数据生成签名或加密内容，确保数据的真实性和完整性。
 * - **公钥用于解密（验签）**：数据接收方使用公钥验证签名或解密内容，确认数据未被篡改且确实来自发送方。
 *
 * 总结：
 * - **加签（加密）**：私钥完成。
 * - **验签（解密）**：公钥完成。
 */
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private UserPaidTrackMapper userPaidTrackMapper;
    @Autowired
    private UserPaidAlbumMapper userPaidAlbumMapper;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private RsaSigner rsaSigner;

    @Autowired
    private StringRedisTemplate redisTemplate;


    @Autowired
    private RabbitService rabbitService;


    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;


    /**
     * 普通的token和jwt生成的token的区别：
     * 1、普通的token没有任何代表性 普通的token不安全
     * 2、jwt有代表性（载荷中自定义信息）  更安全（可以防止被篡改）
     *
     * @param code
     * @return
     */
    @Override
    public Map<String, Object> wxLogin(String code) {

        Map<String, Object> map = new HashMap<String, Object>();

        String openid = "";
        // 1.判断code码是否存在
        if (StringUtils.isEmpty(code)) {
            throw new GuiguException(201, "code不存在");
        }

        // 2.调用微信服务端
        WxMaUserService userService = wxMaService.getUserService();
        WxMaJscode2SessionResult sessionInfo = null;
        try {
            sessionInfo = userService.getSessionInfo(code);
            openid = sessionInfo.getOpenid();
        } catch (WxErrorException e) {
            log.error("调用微信服务端失败:{}", e.getMessage());
            throw new GuiguException(201, "调用微信服务端失败");
        }

        ////user:login:refresh:openid
        String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openid;//拿到刷新token的key
        //通过刷新token从redis中获取
        String jsonWebTokenFromRedis = redisTemplate.opsForValue().get(refreshTokenKey);
        //如果存在，直接返回
        if (!StringUtils.isEmpty(jsonWebTokenFromRedis)) {

            Map<String, Object> map1 = new HashMap<>();
            map1.put("token", jsonWebTokenFromRedis);
            return map1;
        }

        // 3.根据openId查询用户信息
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getWxOpenId, openid);
        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
        if (userInfo == null) {
            // 1.像user_info表中插入用户（注册用户信息）
            userInfo = new UserInfo();
            userInfo.setWxOpenId(openid);
            userInfo.setNickname(System.currentTimeMillis() + "-【hzk】-" + UUID.randomUUID().toString().substring(0, 4).replace("-", ""));  // 昵称
            userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
            userInfo.setIsVip(0);  // 不是vip
            userInfo.setVipExpireTime(new Date());
            int insert = userInfoMapper.insert(userInfo);
            log.info("注册用户：{}", insert > 0 ? "success" : "fail");
            //  像tingshu_account库中的user_account表中插入用户账户（初始化用户账户余额）
            /**
             * param1: 交换机
             * param2: 路由键
             * param3: 消息内容
             */
            rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());
            log.info("用户微服务发送初始化用户账户余额消息：{}成功", userInfo.getId());

        }


        // 4.得到jsonWebToken
        String token = getJsonWebToken(openid, userInfo.getId());
        //存放在服务端的内存中（Session）  MySQL(一个微服务 各个微服务都能取到 比较的时候比较慢)   Redis（一个微服务 各个微服务都能取到  比较时候比较快）


        // Redis常用数据类型有五种（String  List  Set  Zset   Hash(Map)）
        // 5. 将jsonWebToken存放到Redis中
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openid;//user:login:openid

        // 给用户登录的信息设置一个过期时间
        // 长 短
        // 短：频繁登录   优点：安全一些（攻击的时间窗口较小）
        // 长：不用频繁登录 缺点：不太安全（攻击的时间窗口较大）
        // 引入一个token (1.为了提高用户的体验  2.安全)
        // 双token机制 accessToken(时间短：一般在分钟或者小时级别) 和RefreshToken （时间长：在天或者月）

//        redisTemplate.opsForValue().set(accessTokenKey, token, 10, TimeUnit.MINUTES);   // 短一点（线上）
        redisTemplate.opsForValue().set(accessTokenKey, token, 20, TimeUnit.DAYS);   //测试环境
        redisTemplate.opsForValue().set(refreshTokenKey, token, 180, TimeUnit.DAYS); // 长一点

        // 6.返回
        map.put("token", token); // token值
        return map;
    }

    /**
     * 代码功能 该行代码创建了一个空的 JSONObject 对象。JSONObject 是 FastJSON 库中的一个类，用于表示 JSON 格式的键值对数据结构。
     * 创建空对象通常是为了后续向其中添加键值对数据。
     * 上下文意义 结合 UserInfoServiceImpl.java 文件内容，此代码片段可能出现在生成 JWT（JSON Web Token）的过程中。具体来说：
     * 后续可能会将用户信息（如 id 和 openId）存入 jsonObject 中。
     * 这些信息会被序列化为字符串，并作为载荷（payload）用于生成 JWT。
     * 实际用途 在 getJsonWebToken 方法中，JSONObject 被用来封装用户的关键信息（如用户 ID 和 OpenID），这些信息会被编码到 JWT 中，以便在后续的请求中验证用户身份。
     * @param openid
     * @param userId
     * @return
     */

    private String getJsonWebToken(String openid, Long userId) {
        //初始化一个空的 JSON 对象，为后续存储用户相关信息做准备，最终用于生成安全的 JWT
        JSONObject jsonObject = new JSONObject();//
        jsonObject.put("id", userId);
        jsonObject.put("openId", openid);
        // jwt方式生成私钥加密的token
        Jwt jwt = JwtHelper.encode(jsonObject.toString(), rsaSigner);

        String encoded = jwt.getEncoded();
        return encoded;
    }

// 获取新的登录令牌
    @Override
    public Map<String, Object> getNewAccessToken() {

        HashMap<String, Object> result = new HashMap<>();
/**首先从请求头中获取前端传递的token值 (1.在 spring mvc 中 前端发送过来的请求 先被 DispatcherServlet 接收到,
 * 2.DispatcherServlet 调用 RequestContextHolder，将当前请求的HttpServletRequest 和 HttpServletResponse
 * 绑定到当前线程的ServletRequestAttributes)
 */
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();//从Spring中获取当前请求的请求头信息
        // 1.2 获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();
        // 1.3 获取请求对象的请求头
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            throw new GuiguException(201, "之前没有登录过");
        }

        // 2.校验jsonWebToken是否被篡改了。解密并验证看是否相等
        /**
         * 解码并验证传入的 token 是否合法。
         * 使用 RsaVerifier 和公钥（PublicConstant.PUBLIC_KEY）对 token 的签名进行校验，确保其未被篡改。
         * 如果校验通过，则返回一个包含解码后载荷（payload）的 Jwt 对象。
         */
        //公钥
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(PublicConstant.PUBLIC_KEY));
        /**
         * 获取解码后的载荷（payload），并将其转换为字符串。
         * 从上一步生成的 Jwt 对象中提取载荷部分（claims），即 token 中存储的自定义信息（如用户 ID、OpenID 等）。
         * 这些信息是以字符串形式存储的 JSON 数据，通常包含用户的标识信息或其他业务数据。
         */
        String claims = jwt.getClaims();
        if (StringUtils.isEmpty(claims)) {
            throw new GuiguException(201, "token被篡改了");
        }
        Map map = JSONObject.parseObject(claims, Map.class);
        Object openId = map.get("openId");
        Object userId = map.get("id");


        String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId;
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
/**
 * 这段代码的主要功能是校验并更新用户的登录令牌（Token），具体逻辑如下：
 *
 * 1. **从 Redis 中获取 Refresh Token**：通过 `refreshTokenKey` 从 Redis 中获取用户的 Refresh Token。
 * 2. **判断 Refresh Token 是否存在**：
 *    - 如果存在，调用 `getJsonWebToken` 方法生成新的 Json Web Token（JWT）。
 *    - 将新生成的 JWT 存入 Redis，设置过期时间为 20 天（测试环境），同时更新 Refresh Token 的有效期为 180 天。
 *    - 将新生成的 Token 返回给客户端。
 * 3. **如果 Refresh Token 不存在**：
 *    - 返回一个标识（如 `"1"`），提示用户需要重新登录。
 *
 * 总结：这段代码实现了基于 Refresh Token 的 Token 更新机制，确保用户在一定时间内无需重复登录，同时保持系统的安全性。
 *
 */

        // 1.从Redis中获取RefreshToken
        String refreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(refreshToken)) {
            String jsonWebToken = getJsonWebToken(String.valueOf(openId.toString()), Long.parseLong(userId.toString()));
            redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 20, TimeUnit.DAYS);   //测试环境
            redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 180, TimeUnit.DAYS); // 长一点
            result.put("token", jsonWebToken);
            return result;

        } else {
            // 去登录
            result.put("1", "v");
        }

        return result;
    }

    @Override
    public void updateUser(UserInfoVo userInfoVo) {

        // 1.查询用户信息
        Long userId = AuthContextHolder.getUserId();

        UserInfo userInfo = userInfoMapper.selectById(userId);
        if (null == userInfo) {
            throw new GuiguException(201, "用户信息不存在");
        }
        userInfo.setNickname(userInfoVo.getNickname());
        userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
        userInfoMapper.updateById(userInfo);
    }

    @Override
    public UserInfoVo getUserInfo(Long userId) {

        UserInfo userInfo = userInfoMapper.selectById(userId);

        if (userInfo == null) {
            throw new GuiguException(201, "用户不存在");
        }
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(userInfo, userInfoVo);

        return userInfoVo;
    }

    @Override
    public Map<Long, String> getUserPaidAlbumTrack(Long userId, Long albumId) {

        LambdaQueryWrapper<UserPaidTrack> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserPaidTrack::getUserId, userId);
        wrapper.eq(UserPaidTrack::getAlbumId, albumId);
        List<UserPaidTrack> userPaidTracks = userPaidTrackMapper.selectList(wrapper);

        Map<Long, String> map = userPaidTracks.stream().collect(Collectors.toMap(UserPaidTrack::getTrackId, v -> "1"));
        return map;
    }

    @Override
    public Boolean getUserPaidAlbum(Long userId, Long albumId) {
        LambdaQueryWrapper<UserPaidAlbum> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserPaidAlbum::getUserId, userId);
        wrapper.eq(UserPaidAlbum::getAlbumId, albumId);
        UserPaidAlbum userPaidAlbum = userPaidAlbumMapper.selectOne(wrapper);
        return userPaidAlbum != null;
    }

    @Override
    public Boolean collect(Long trackId) {

        Long userId = AuthContextHolder.getUserId();

        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);

        // 2.构建查询对象
        Query query = new Query(criteria);

        // 3.开始查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        UserCollect userCollect = mongoTemplate.findOne(query, UserCollect.class, collectionName);
        if (userCollect == null) {
            // 收藏声音
            // 插入收藏对象到MongoDB
            userCollect = new UserCollect();
            userCollect.setId(ObjectId.get().toString());
            userCollect.setUserId(userId);
            userCollect.setTrackId(trackId);
            userCollect.setCreateTime(new Date());
            mongoTemplate.save(userCollect, collectionName);
            TrackStatMqVo trackStatMqVo = prepareTrackStatMqDto(null, userCollect.getTrackId(), SystemConstant.TRACK_STAT_COLLECT, 1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));

            return true;  // 收藏过
        } else {
            // 取消收藏
            mongoTemplate.remove(query, UserCollect.class, collectionName);
            TrackStatMqVo trackStatMqVo = prepareTrackStatMqDto(null, userCollect.getTrackId(), SystemConstant.TRACK_STAT_COLLECT, -1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return false;

        }
    }

    @Override
    public TrackStatMqVo prepareTrackStatMqDto(Long albumId, Long trackId, String trackStatType, int count) {
        TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
        trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replace("-", "")); // 消息去重
        trackStatMqVo.setAlbumId(albumId);
        trackStatMqVo.setTrackId(trackId);
        trackStatMqVo.setStatType(trackStatType);
        trackStatMqVo.setCount(count);
        return trackStatMqVo;
    }

    @Override
    public Boolean isCollect(Long trackId) {


        Long userId = AuthContextHolder.getUserId();

        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);

        // 2.构建查询对象
        Query query = new Query(criteria);

        long count = mongoTemplate.count(query, UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
        return count > 0;
    }

    @Override
    public Boolean isSubscribe(Long albumId) {


        Long userId = AuthContextHolder.getUserId();

        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("albumId").is(albumId);

        // 2.构建查询对象
        Query query = new Query(criteria);

        long count = mongoTemplate.count(query, UserSubscribe.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_SUBSCRIBE, userId));
        return count > 0;
    }

    @Override
    public IPage<UserCollectVo> findUserCollectPage(IPage<UserCollectVo> pageParam) {

        Long userId = AuthContextHolder.getUserId();
        // 1.构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId);

        // 2.构建查询对象
        Query query = new Query(criteria);

        // 3.构建分页和排序条件对象【mp中分页查询对MySQL查询有效】

        Sort sort = Sort.by(Sort.Order.desc("updateTime"));
        // from:(pn-1)*size   size
        PageRequest pageAndSort = PageRequest.of((int) ((pageParam.getCurrent() - 1) * pageParam.getSize()), (int) pageParam.getSize(), sort);
        query.with(pageAndSort);

        // 4.查询总记录数
        long count = mongoTemplate.count(query.limit(-1), UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));


        // 5.查询数据
        List<UserCollect> userCollectList = mongoTemplate.find(query, UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));

        List<Long> trackIdList = userCollectList.stream().map(UserCollect::getTrackId).collect(Collectors.toList());
        // 6.根据声音id集合 查询【声音对象】集合
        Result<List<TrackListVo>> trackListVoResult = albumInfoFeignClient.getTrackListByIds(trackIdList);
        List<TrackListVo> trackListVos = trackListVoResult.getData();
        if (CollectionUtils.isEmpty(trackListVos)) {
            throw new GuiguException(201, "远程查询专辑微服务获取声音集合失败");
        }

        //  7.将声音对象的列表集合转成声音对象的Map集合
        Map<Long, TrackListVo> trackListVoMap = trackListVos.stream().collect(Collectors.toMap(TrackListVo::getTrackId, v -> v));


        List<UserCollectVo> userCollectVoList = userCollectList.stream().map(userCollect -> {
            UserCollectVo userCollectVo = new UserCollectVo();
            TrackListVo trackListVo = trackListVoMap.get(userCollect.getTrackId());
            userCollectVo.setAlbumId(trackListVo.getAlbumId());// 收藏声音对应的专辑id
            userCollectVo.setTrackId(userCollect.getTrackId());
            userCollectVo.setCreateTime(userCollect.getCreateTime());
            userCollectVo.setTrackTitle(trackListVo.getTrackTitle());  // 收藏声音标题
            userCollectVo.setCoverUrl(trackListVo.getCoverUrl());  // 收藏声音的封面
            return userCollectVo;
        }).collect(Collectors.toList());
        return pageParam.setRecords(userCollectVoList).setTotal(count);
    }

    @Override
    public void updateExpireVip() {

        // 1.查询过期的vip
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getIsVip, 1);
        queryWrapper.lt(UserInfo::getVipExpireTime, new Date());
        queryWrapper.eq(UserInfo::getIsDeleted, 0);


        List<UserInfo> userInfos = userInfoMapper.selectList(queryWrapper);

        // 2.修改
        userInfos.stream().forEach(userInfo -> {
            userInfo.setIsVip(0);
            userInfoMapper.updateById(userInfo);
        });

    }

}
